package ouhenry.stage.common.config;

/**
 * 雪花算法组成： 标识位+时间戳+数据中心+机器标识+序列号 这里将数据中心+机器标识放到了最高位，使得最大数尽可能地减小，
 * 这么做的主要目的是为了避免页面long精度丢失的问题。最大支持数， 9007199254740991共计53位1
 * <p>
 * 0+机器标识+时间戳+序列号 0+5+16+32
 */
public class SnowFlake {

    /** 起始时间戳，精确到秒，不可大于当前系统时间 2021-08-01 00:00:00 */
    private final static long INITIAL_TIMESTAMP = -1627747200L;

    // /** 时间戳占用的位数 ，32位 */
    // private final static long TIMESTAMP_BIT = 32;

    /** 序列号占用的位数 ，16位 */
    private final static long SEQUENCE_BIT = 16;

    /** 机器标识占用的位数，5位32个 */
    private final static long MACHINE_BIT = 5;

    /** 序列号支持的最大长整型数 即2的16次方减1 */
    private final static long SEQUENCE_MAX_VALUE = -1L ^ (-1L << SEQUENCE_BIT);

    /** 支持的最大机器id，结果是31 */
    private final static long MACHINE_MAX_NUM = -1L ^ (-1L << MACHINE_BIT);

    /** 机器标识位向左移动序列号宽度的位数，使得机器标识位通过或运算加在时间戳后面， */
    private final static long MACHINE_SHIFT_LEFT = SEQUENCE_BIT;

    /** 时间戳向左移动序列号的位数，移动为最高位 */
    private final static long TIMESTMP_SHIFT_LEFT = SEQUENCE_BIT + MACHINE_BIT;

    private static long machine; // 机器标识
    private static long sequence = 0L; // 序列号
    private static long lastTimestamp = -1L;// 上一次时间戳

    /**
     * 构造函数，初始化机器标识
     * 
     * @param machine
     */
    public SnowFlake(long machine) {
        if (machine > MACHINE_MAX_NUM || machine < 0) {
            throw new IllegalArgumentException("machine can't be greater than " + MACHINE_MAX_NUM + " or less than 0");
        }
        SnowFlake.machine = machine;
    }

    /**
     * 产生下一个ID
     * 
     * @return
     */
    public synchronized long nextId() {
        long currentTimestamp = System.currentTimeMillis() / 1000;
        // 如果当前时间小于上一次ID生成的时间戳，说明系统时钟回退，过这个时候应当抛出异常
        if (currentTimestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards. Refusing to generate id");
        }

        // 如果是同一时间生成的，则进行毫秒内序列。相同毫秒内，序列号自增
        if (currentTimestamp == lastTimestamp) {
            sequence = (sequence + 1) & SEQUENCE_MAX_VALUE;
            // 同一毫秒的序列数已经达到最大，则阻塞到下一个毫秒,获得新的时间戳，循环获取多几次，尽可能地避免不可能的可能
            if (sequence == 0L) {
                for (int i = 0; i < 100; i++) {
                    currentTimestamp = getNextTimestamp();
                    if (currentTimestamp != lastTimestamp) {
                        break;
                    }
                }
            }
        } else {
            sequence = 0L;// 不同毫秒内，序列号置为0
        }

        lastTimestamp = currentTimestamp;
        // 时间戳部分，左移到高位。初始时间戳越大，这部分可生成的数就越多。
        // 机器标识部分
        // 序列号部分
        return (currentTimestamp - INITIAL_TIMESTAMP) << TIMESTMP_SHIFT_LEFT | machine << MACHINE_SHIFT_LEFT | sequence;
    }

    /**
     * 获取下一个晚于上一次时间的时间戳
     * 
     * @return
     */
    private long getNextTimestamp() {
        long timestamp = System.currentTimeMillis() / 1000;
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis() / 1000;
        }
        return timestamp;
    }

    /**
     * 静态工具类
     *
     * @return
     */
    public static synchronized Long generateId() {
        SnowFlake snowFlake = new SnowFlake(0);
        return snowFlake.nextId();
    }

    /**
     * 根据机器编号生成id，最大支持31
     * 
     * @param machine
     * @return
     */
    public static synchronized Long generateId(int machine) {
        SnowFlake snowFlake = new SnowFlake(machine);
        return snowFlake.nextId();
    }

    public static void main(String[] args) {
        SnowFlake snowFlake = new SnowFlake(1);

        long start = System.currentTimeMillis();
        for (int i = 0; i < 65535; i++) {
            System.out.println(snowFlake.nextId());
        }
        System.out.println(System.currentTimeMillis() - start);
    }

}
